0. Requirements
Note: Please load the workspace in the directory
implementation/R/workspace/corpus_methods.RData to run the
following code and re-use the previously created variables (to avoid
long running times). Furthermore, the following libraries must be
installed and loaded:
# intall necessary packages
#install.packages("quanteda")
#install.packages("readtext")
#install.packages("tidyverse")
#install.packages("quanteda.textstats")
#install.packages("quanteda.textplots")
#install.packages("data.table")
#install.packages("stringr")
#install.packages("spacyr")
#install.packages("textcat")
#install.packages("plyr")
# load libraries
library(quanteda)
library(readtext)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ───────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5 ✓ purrr 0.3.4
✓ tibble 3.1.6 ✓ dplyr 1.0.8
✓ tidyr 1.2.0 ✓ stringr 1.4.0
✓ readr 2.1.2 ✓ forcats 0.5.1
── Conflicts ──────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
library(quanteda.textplots)
library(quanteda.textstats)
library(plyr)
--------------------------------------------------------------------------------------------
You have loaded plyr after dplyr - this is likely to cause problems.
If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
library(plyr); library(dplyr)
--------------------------------------------------------------------------------------------
Attache Paket: ‘plyr’
Die folgenden Objekte sind maskiert von ‘package:dplyr’:
arrange, count, desc, failwith, id, mutate, rename, summarise, summarize
Das folgende Objekt ist maskiert ‘package:purrr’:
compact
library(dplyr)
#library(stringr)
#library(data.table)
#library(textcat)
1. Collocations
To retrieve the collocations, i.e. in our case one token to the left
or right of the compound word, we use the kwic function
offered by quanteda. Here we can choose a window of 1 to
make sure we obtain the correct number of collocations.
1.1 First Look
Let’s have a first look at the collocations for the example
Klimaleugner (en: “climate denier”). We are going to retrieve
the collocations to the left (pre) and to the right
(post) of the key word and count their occurrences. Then,
we will output the Top-5 collocations for each category,
i.e. pre and post.
# apply keyword-in-context function for given word
word = "klimaleugner"
# to C2022
kwic_con <- kwic(sp_c2022_tokens, pattern=word, window=1, valuetype="fixed") %>%
as_tibble()
# to P2022
kwic_pro <- kwic(sp_p2022_tokens, pattern=word, window=1, valuetype="fixed") %>%
as_tibble()
Let’s show the Top-5 for the C2022 corpus:
kwic_con %>%
dplyr::count(pre) %>%
arrange(desc(n)) %>%
head(n=5)
kwic_con %>%
dplyr:: count(post) %>%
arrange(desc(n)) %>%
head(n=5)
And the Top-5 for the P2022 corpus:
kwic_pro %>%
dplyr::count(pre) %>%
arrange(desc(n)) %>%
head(n=5)
kwic_pro %>%
dplyr::count(post) %>%
arrange(desc(n)) %>%
head(n=5)
1.2 Apply to all Glossary Terms
Now we seek to create tables that contain the top 5 pre
and post collocations for each of our compound words.
Firstly, we create a table for the collocations we can obtain from
P2022
# for each compound, get list of top 5 collocations
# initiate empty data frame
pro_colls10 = data.frame()
# for each compound
for (word in compounds){
# get collocations
kwic_pro <- kwic(sp_p2022_tokens, pattern=word, window=1, valuetype="fixed") %>%
as_tibble()
keyword <- word
# retrieve top5 preceding collocations
pro_pre <- kwic_pro %>%
dplyr::count(pre) %>%
arrange(desc(n)) %>%
head(n=5)
# retrieve top5 following collocations
pro_post <- kwic_pro %>%
dplyr::count(post) %>%
arrange(desc(n)) %>%
head(n=5)
# normalize data frames with top5 collocations
pro_pre$keyword <- keyword
pro_pre$tag <- "pre"
names(pro_pre)[names(pro_pre) == 'pre'] <- "word"
pro_post$keyword <- keyword
pro_post$tag <- "post"
names(pro_post)[names(pro_post) == 'post'] <- "word"
pro_colls10 <- rbind(pro_colls10, pro_pre)
pro_colls10 <- rbind(pro_colls10, pro_post)}
Most of the collocations only occur exactly once. Since this is not
very informative for us, we remove all the collocations with a count of
exactly 1. Also, we want to remove noise, i.e. empty strings from the
collocations.
# only keep collocations that appear more than once
top_colls_pro<-pro_colls10[(pro_colls10$n > 1),]
# remove empty strings
top_colls_pro<-top_colls_pro[(top_colls_pro$word > " "),]
And save the table to a csv file.
#write.csv(top_colls_pro, "../output/top_collocations_pro.csv")
Then, we create the same table of the top 5 pre and
post collocations for the C2022.
# for each compound, get list of top 5 collocations
# initiate empty data frame
con_colls10 = data.frame()
# for each compound
for (word in compounds){
# get collocations
kwic_con <- kwic(sp_c2022_tokens, pattern=word, window=1, valuetype="fixed") %>%
as_tibble()
#keyword <- kwic_pro$keyword[[1]]
keyword <- word
# retrieve top5 preceding collocations
con_pre <- kwic_con %>%
dplyr::count(pre) %>%
arrange(desc(n)) %>%
head(n=5)
# retrieve top5 following collocations
con_post <- kwic_con %>%
dplyr::count(post) %>%
arrange(desc(n)) %>%
head(n=5)
# normalize data frames with top5 collocations
con_pre$keyword <- keyword
con_pre$tag <- "pre"
names(con_pre)[names(con_pre) == 'pre'] <- "word"
con_post$keyword <- keyword
con_post$tag <- "post"
names(con_post)[names(con_post) == 'post'] <- "word"
con_colls10 <- rbind(con_colls10, con_pre)
con_colls10 <- rbind(con_colls10, con_post)}
And, just like before, we remove the collocations that appeared only
once in the corpus (and remove noise, i.e. empty strings from the
collocations).
# only keep collocations that appear more than once
top_colls_con<-con_colls10[(con_colls10$n > 1),]
# remove empty strings
top_colls_con<-top_colls_con[(top_colls_con$word > " "),]
And save the final table to a csv file.
write.csv(top_colls_con, "../output/top_collocations_con1.csv")
2. Concordances (KWIC)
To retrieve the context of each compound word, we extract the
concordances on a sentence level. That means, we extract a window of 5
sentences to the left and to the right of the keyword sentence. To do
this, we have to tokenise our data by sentences, instead of words.
2.1 Preprocessing
Since we cannot normalise the data the same way when we create tokens
on a sentence-level, we firstly create word-level tokens from the
corpora.
# create word tokens for P2022 and C2022
p2022_tokens <- tokens(pro2022, remove_punct = FALSE, remove_symbols = TRUE,
remove_numbers = TRUE, remove_url = TRUE, remove_separators = TRUE)
c2022_tokens <- tokens(contra2022, remove_punct = FALSE, remove_symbols = TRUE,
remove_numbers = TRUE, remove_url = TRUE, remove_separators = TRUE)
To these tokens, we apply a normalisation step where we remove
hyphens within words, such as “Klima-Skeptiker” to convert it to
“Klimaskeptiker”.
# remove hyphens from tokens
# convert to tokens
#p2022_toks_cleaned <- as.tokens(p2022_tokens)
# replace multi-token sequences with a "compound" token
#toks_comp <- tokens_compound(p2022_toks_cleaned, phrase("*-*"), concatenator ="")
toks_comp_p <- tokens_compound(p2022_tokens, phrase("*-*"), concatenator ="")
# get tokens containing the hyphen
toks_hyphenated_p <- grep("\\w+-\\w+", types(toks_comp_p), value = TRUE)
# replace the hyphenated tokens by versions without hyphen
p2022_toks_cleaned <- tokens_replace(toks_comp_p, toks_hyphenated_p, gsub("-", "", toks_hyphenated_p))
# convert to tokens
#c2022_toks_cleaned <- as.tokens(c2022_tokens)
#toks_comp <- tokens_compound(c2022_toks_cleaned, phrase("*-*"), concatenator ="")
toks_comp_c <- tokens_compound(c2022_tokens, phrase("*-*"), concatenator ="")
# get tokens containing the hyphen
toks_hyphenated_c <- grep("\\w+-\\w+", types(toks_comp_c), value = TRUE)
# replace the hyphenated tokens by versions without hyphen
c2022_toks_cleaned <- tokens_replace(toks_comp_c, toks_hyphenated_c, gsub("-", "", toks_hyphenated_c))
# merge tokens back into corpus object
p2022_merged_toks <- corpus(sapply(p2022_toks_cleaned, paste, collapse = " "))
c2022_merged_toks <- corpus(sapply(c2022_toks_cleaned, paste, collapse = " "))
Now we can create cleaned sentence tokens for both corpora.
# create "sentence" tokens for P2022 and C2022 corpus
p2022_sentences <- tokens(p2022_merged_toks, remove_punct = FALSE, remove_symbols = TRUE,
remove_numbers = TRUE, remove_url = TRUE, remove_separators = TRUE,
what = "sentence")
c2022_sentences <- tokens(c2022_merged_toks, remove_punct = FALSE, remove_symbols = TRUE,
remove_numbers = TRUE, remove_url = TRUE, remove_separators = TRUE,
what = "sentence")
2.2 Key Word In Context Retrieval
To retrieve the concordances of each compound word, we apply the
Key-Word-In-Context function given by
quanteda. For this, we use the previously created sentence
tokens.
# create a data frame from tokens containing 5 sentences before and after the keyword
### DO FOR p2022 ####
kwic_pro_sent.df <- data.frame(matrix(ncol = 7, nrow = 0))
kwiclist_sent_pro <- list()
# for each compound word
for (word in compounds)
{
# retrieve sentences before/after keyword
context_pro_sent <- kwic(p2022_sentences, word, valuetype="regex", window=5)
kwiclist_sent_pro[[word]] <- context_pro_sent # save to list
}
kwic_pro_sent.df = do.call(rbind, kwiclist_sent_pro) # save to final data frame
### DO FOR c2022 ###
kwic_con_sent.df <- data.frame(matrix(ncol = 7, nrow = 0))
kwiclist_sent_con <- list()
# for each compound word
for (word in compounds)
{
# retrieve sentences before/after keyword
context_con_sent <- kwic(c2022_sentences, word, valuetype="regex", window=5)
kwiclist_sent_con[[word]] <- context_con_sent # save to list
}
kwic_con_sent.df = do.call(rbind, kwiclist_sent_con) # save to final data frame
kwic_pro_sent.df
Keyword-in-context with 803 matches.
kwic_con_sent.df
Keyword-in-context with 1,946 matches.
[ reached max_nrow ... 946 more matches ]
2.2 Export Concordances
Next, we export the concordances to the files
pro_context and con_context.
# save to csv file
write.csv(kwic_pro_sent.df,"../output/pro_context_new.csv", row.names = FALSE)
write.csv(kwic_con_sent.df,"../output/con_context_new.csv", row.names = FALSE)
3. Term Frequencies
Additionally, we compute the term frequencies of each compound word
and the according TF-IDF score, since both corpora have a different size
and we want to explore the relevance of each term.
Create a function to normalize the TF-IDF scores
# min/max normalization from -1 to 1, relative to data frame results
normalize <- function(x, na.rm = TRUE){
return((x - min(x)) / (max(x)-min(x)))}
We use the preprocessed tokens from the preprocessing
notebook (the variables are loaded from the workspace). We re-combine
these tokens to generate two corpus objects, one for each subdiscourse.
Then we can assign groups to the two corpora and generate a full corpus
variable complete that contains both corpora. This step
enables us to directly retrieve the term frequencies for both corpora
and to be able to get a comparison table of the frequencies.
# use lemmatised tokens to re-create a corpus
# we need this step for the grouping of the frequencies
p2022_cleaned <- corpus(sapply(sp_p2022_tokens, paste, collapse = " "))
c2022_cleaned <- corpus(sapply(sp_c2022_tokens, paste, collapse = " "))
# create groups
p2022_cleaned$group <- "Supporters"
c2022_cleaned$group <- "Skeptics"
# create a corpus containing both subdiscourses
complete = p2022_cleaned+c2022_cleaned
Now we create a DFM with the frequencies for which we only keep the
compound words, since we are only interested in the term frequencies of
the glossary terms. This DFM is then converted into a dataframe which
contains the term frequencies for both subdiscourses. This can now be
saved to a final file
textplot_wordcloud(dfm_complete_freq, comparison = TRUE, max_words = 250) # plot wordcloud
Warnung in wordcloud_comparison(x, min_size, max_size, min_count, max_words,
KLIMAGERECHTIGKEIT could not be fit on page. It will not be plotted.

3.1 Compute TF-IDF Scores
Next, we compute the TF-IDF scores of the glossary terms. For this,
we create DFMs for each subdiscourse. This is necessary to compute the
TF-IDF scores for each term with respect to each of the corpora.
### FOR C2022
# create dfm of lemmatized tokens, only keep compound words
#dfm_c2022 <- dfm(sp_c2022_tokens) %>% dfm_keep(pattern = compounds)
#dfm_c2022_tfidf <- dfm_tfidf(dfm_c2022) # compute tfidf scores
#top_c2022_norm <- normalize(topfeatures(dfm_c2022, n=300)) # normalize scores
dfm_c2022 <- dfm(sp_c2022_tokens) %>%
dfm_tfidf() %>%
dfm_keep(pattern = compounds)
c2022_tfidf <- normalize(topfeatures(dfm_c2022, n=300))
# dfm_keep(pattern = compounds)
#dfm_c2022_tfidf <- dfm_tfidf(dfm_c2022) # compute tfidf scores
#top_c2022_norm <- normalize(topfeatures(dfm_c2022, n=300))
# convert dfm into data frame
top_c2022_norm <- data.frame(Term = names(c2022_tfidf), Freq = c2022_tfidf, row.names = NULL) %>%
dplyr::arrange(desc(Freq))
### FOR P2022
# create dfm of lemmatized tokens, only keep compound words
#dfm_p2022 <- dfm(sp_p2022_tokens) %>% dfm_keep(pattern = compounds)
#dfm_p2022_tfidf <- dfm_tfidf(dfm_p2022) # compute tfidf scores
#top_p2022_norm <- normalize(topfeatures(dfm_p2022, n=300)) # normalize scores
dfm_p2022 <- dfm(sp_p2022_tokens) %>%
dfm_tfidf() %>%
dfm_keep(pattern = compounds)
p2022_tfidf <- normalize(topfeatures(dfm_p2022, n=300))
# convert dfm into data frame
top_p2022_norm <- data.frame(Term = names(p2022_tfidf), Freq = p2022_tfidf, row.names = NULL) %>%
dplyr::arrange(desc(Freq))
# change column names to be able to merge both data frames
colnames(top_p2022_norm)[2] <- "Freq_P2022"
colnames(top_c2022_norm)[2] <- "Freq_C2022"
# merge data frames
df_merge <- merge(top_c2022_norm,top_p2022_norm,by="Term", all.x = TRUE, all.y = TRUE)
# write to csv file
write.csv(df_merge,"/Users/anna/Documents/uni/thesis/implementation/R/output/tfidf_complete.csv", row.names = TRUE)
Plot TF-IDF Scores
To visualize a comparison of the TF-IDF scores of the compound for
each of the subdiscourses, we create the following plot of a sample of
50 glossary terms and their according scores.
# retrieve frequency table of dfm
freqs_pro <- textstat_frequency(dfm_p2022, force=TRUE)
freqs_con <- textstat_frequency(dfm_c2022, force=TRUE)
# capitalize first letter of compound
freqs_pro$feature <- str_to_title(freqs_pro$feature)
freqs_con$feature <- str_to_title(freqs_con$feature)
# apply normalization
freqs_pro$normalize = round(normalize(freqs_pro$frequency),3)
freqs_con$normalize = round(normalize(freqs_con$frequency),3)
# plot comparison of both groups
freqs.act <- filter(freqs_pro) %>% as.data.frame() %>% select(feature, normalize)
freqs.scept <- filter(freqs_con) %>% as.data.frame() %>% select(feature, normalize)
freqs <- left_join(freqs.act, freqs.scept, by = "feature") %>% head(30) %>% arrange(normalize.x) %>% mutate(feature = factor(feature, feature))
# create plot
plot8 <- ggplot(freqs) +
geom_segment(aes(x=feature, xend=feature, y=normalize.x, yend=normalize.y), color="grey") +
geom_point(aes(x=feature, y=normalize.x, colour="Supporters"), size = 3) +
geom_point(aes(x=feature, y=normalize.y, colour="Skeptics"), size = 3) +
ggtitle("Comparison TF-IDF Scores per Sub Discourse") +
xlab("") + ylab("TF-IDF") +
coord_flip()
plot8+labs(colour="Group")
Warnung: Removed 8 rows containing missing values (geom_segment).
Warnung: Removed 8 rows containing missing values (geom_point).

# save to png
#ggsave("/Users/anna/Documents/uni/thesis/plots/comparison_tfidf.png", dpi=300, dev='png', height=6, width=11, units="in")
DO THIS AND THEN SAVE WORKSPACE!!!!
sp_c2022_tokens <- tokens_replace(sp_c2022_tokens, pattern="klimaglaubenslehr", replacement="klimaglaubenslehre", valuetype = "fixed")
sp_c2022_tokens <- tokens_replace(sp_c2022_tokens, pattern="klimakarawan", replacement="klimakarawane", valuetype = "fixed")
sp_c2022_tokens <- tokens_replace(sp_c2022_tokens, pattern="klimazeug", replacement="klimazeugs", valuetype = "fixed")
sp_c2022_tokens <- tokens_replace(sp_c2022_tokens, pattern="klimawendehal", replacement="klimawendehals", valuetype = "fixed")
sp_p2022_tokens <- tokens_replace(sp_p2022_tokens, pattern="klimaglaubenslehr", replacement="klimaglaubenslehre", valuetype = "fixed")
sp_p2022_tokens <- tokens_replace(sp_p2022_tokens, pattern="klimakarawan", replacement="klimakarawane", valuetype = "fixed")
sp_p2022_tokens <- tokens_replace(sp_p2022_tokens, pattern="klimazeug", replacement="klimazeugs", valuetype = "fixed")
sp_p2022_tokens <- tokens_replace(sp_p2022_tokens, pattern="klimawendehal", replacement="klimawendehals", valuetype = "fixed")
LS0tCnRpdGxlOiAiQ29ycHVzLUJhc2VkIE1ldGhvZHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgMC4gUmVxdWlyZW1lbnRzCk5vdGU6IFBsZWFzZSBsb2FkIHRoZSB3b3Jrc3BhY2UgaW4gdGhlIGRpcmVjdG9yeSBgaW1wbGVtZW50YXRpb24vUi93b3Jrc3BhY2UvY29ycHVzX21ldGhvZHMuUkRhdGFgIHRvIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgYW5kIHJlLXVzZSB0aGUgcHJldmlvdXNseSBjcmVhdGVkIHZhcmlhYmxlcyAodG8gYXZvaWQgbG9uZyBydW5uaW5nIHRpbWVzKS4gRnVydGhlcm1vcmUsIHRoZSBmb2xsb3dpbmcgbGlicmFyaWVzIG11c3QgYmUgaW5zdGFsbGVkIGFuZCBsb2FkZWQ6CmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgaW50YWxsIG5lY2Vzc2FyeSBwYWNrYWdlcwojaW5zdGFsbC5wYWNrYWdlcygicXVhbnRlZGEiKQojaW5zdGFsbC5wYWNrYWdlcygicmVhZHRleHQiKQojaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKI2luc3RhbGwucGFja2FnZXMoInF1YW50ZWRhLnRleHRzdGF0cyIpCiNpbnN0YWxsLnBhY2thZ2VzKCJxdWFudGVkYS50ZXh0cGxvdHMiKQojaW5zdGFsbC5wYWNrYWdlcygicGx5ciIpCgojIGxvYWQgbGlicmFyaWVzCmxpYnJhcnkocXVhbnRlZGEpCmxpYnJhcnkocmVhZHRleHQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHF1YW50ZWRhLnRleHRwbG90cykKbGlicmFyeShxdWFudGVkYS50ZXh0c3RhdHMpCmxpYnJhcnkocGx5cikKbGlicmFyeShkcGx5cikKYGBgCgojIDEuIENvbGxvY2F0aW9ucwpUbyByZXRyaWV2ZSB0aGUgY29sbG9jYXRpb25zLCBpLmUuIGluIG91ciBjYXNlIG9uZSB0b2tlbiB0byB0aGUgbGVmdCBvciByaWdodCBvZiB0aGUgY29tcG91bmQgd29yZCwgd2UgdXNlIHRoZSBga3dpY2AgZnVuY3Rpb24gb2ZmZXJlZCBieSBgcXVhbnRlZGFgLiBIZXJlIHdlIGNhbiBjaG9vc2UgYSB3aW5kb3cgb2YgMSB0byBtYWtlIHN1cmUgd2Ugb2J0YWluIHRoZSBjb3JyZWN0IG51bWJlciBvZiBjb2xsb2NhdGlvbnMuCgojIyAxLjEgRmlyc3QgTG9vawpMZXQncyBoYXZlIGEgZmlyc3QgbG9vayBhdCB0aGUgY29sbG9jYXRpb25zIGZvciB0aGUgZXhhbXBsZSAqS2xpbWFsZXVnbmVyKiAoZW46ICJjbGltYXRlIGRlbmllciIpLiBXZSBhcmUgZ29pbmcgdG8gcmV0cmlldmUgdGhlIGNvbGxvY2F0aW9ucyB0byB0aGUgbGVmdCAoYHByZWApIGFuZCB0byB0aGUgcmlnaHQgKGBwb3N0YCkgb2YgdGhlIGtleSB3b3JkIGFuZCBjb3VudCB0aGVpciBvY2N1cnJlbmNlcy4gVGhlbiwgd2Ugd2lsbCBvdXRwdXQgdGhlIFRvcC01IGNvbGxvY2F0aW9ucyBmb3IgZWFjaCBjYXRlZ29yeSwgaS5lLiBgcHJlYCBhbmQgYHBvc3RgLiAKYGBge3J9CiMgYXBwbHkga2V5d29yZC1pbi1jb250ZXh0IGZ1bmN0aW9uIGZvciBnaXZlbiB3b3JkCndvcmQgPSAia2xpbWFsZXVnbmVyIgoKIyB0byBDMjAyMgprd2ljX2NvbiA8LSBrd2ljKHNwX2MyMDIyX3Rva2VucywgcGF0dGVybj13b3JkLCB3aW5kb3c9MSwgdmFsdWV0eXBlPSJmaXhlZCIpICU+JQogIGFzX3RpYmJsZSgpCgojIHRvIFAyMDIyCmt3aWNfcHJvIDwtIGt3aWMoc3BfcDIwMjJfdG9rZW5zLCBwYXR0ZXJuPXdvcmQsIHdpbmRvdz0xLCB2YWx1ZXR5cGU9ImZpeGVkIikgJT4lCiAgYXNfdGliYmxlKCkKYGBgCgpMZXQncyBzaG93IHRoZSBUb3AtNSBmb3IgdGhlIEMyMDIyIGNvcnB1czoKYGBge3J9CiMgY291bnQgb2NjdXJyZW5jZXMsIHNvcnQgZGVzY2VuZGluZyBhbmQgZ2l2ZSB0b3AgNSAicHJlIiBjb2xsb2NhdGlvbnMKa3dpY19jb24gJT4lCiAgZHBseXI6OmNvdW50KHByZSkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBoZWFkKG49NSkKCiMgY291bnQgb2NjdXJyZW5jZXMsIHNvcnQgZGVzY2VuZGluZyBhbmQgZ2l2ZSB0b3AgNSAicG9zdCIgY29sbG9jYXRpb25zCmt3aWNfY29uICU+JQogIGRwbHlyOjogY291bnQocG9zdCkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBoZWFkKG49NSkgCmBgYAoKQW5kIHRoZSBUb3AtNSBmb3IgdGhlIFAyMDIyIGNvcnB1czoKYGBge3J9CiMgY291bnQgb2NjdXJyZW5jZXMsIHNvcnQgZGVzY2VuZGluZyBhbmQgZ2l2ZSB0b3AgNSAicHJlIiBjb2xsb2NhdGlvbnMKa3dpY19wcm8gJT4lCiBkcGx5cjo6Y291bnQocHJlKSAlPiUKICBhcnJhbmdlKGRlc2MobikpICU+JQogIGhlYWQobj01KQoKIyBjb3VudCBvY2N1cnJlbmNlcywgc29ydCBkZXNjZW5kaW5nIGFuZCBnaXZlIHRvcCA1ICJwb3N0IiBjb2xsb2NhdGlvbnMKa3dpY19wcm8gJT4lCiBkcGx5cjo6Y291bnQocG9zdCkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBoZWFkKG49NSkKYGBgCgojIyAxLjIgQXBwbHkgdG8gYWxsIEdsb3NzYXJ5IFRlcm1zCk5vdyB3ZSBzZWVrIHRvIGNyZWF0ZSB0YWJsZXMgdGhhdCBjb250YWluIHRoZSB0b3AgNSBgcHJlYCBhbmQgYHBvc3RgIGNvbGxvY2F0aW9ucyBmb3IgZWFjaCBvZiBvdXIgY29tcG91bmQgd29yZHMuIApGaXJzdGx5LCB3ZSBjcmVhdGUgYSB0YWJsZSBmb3IgdGhlIGNvbGxvY2F0aW9ucyB3ZSBjYW4gb2J0YWluIGZyb20gUDIwMjIKYGBge3J9CiMgZm9yIGVhY2ggY29tcG91bmQsIGdldCBsaXN0IG9mIHRvcCA1IGNvbGxvY2F0aW9ucwojIGluaXRpYXRlIGVtcHR5IGRhdGEgZnJhbWUgCnByb19jb2xsczEwID0gZGF0YS5mcmFtZSgpCgojIGZvciBlYWNoIGNvbXBvdW5kCmZvciAod29yZCBpbiBjb21wb3VuZHMpewoKICAjIGdldCBjb2xsb2NhdGlvbnM6IGFwcGx5IGt3aWMgZnVuY3Rpb24gYW5kIHR1cm4gaW50byB0YWJsZSAKICBrd2ljX3BybyA8LSBrd2ljKHNwX3AyMDIyX3Rva2VucywgcGF0dGVybj13b3JkLCB3aW5kb3c9MSwgdmFsdWV0eXBlPSJmaXhlZCIpICU+JQogICAgYXNfdGliYmxlKCkgCiAgCiAgIyBzZXQgd29yZCBhcyBrZXkgd29yZCAKICBrZXl3b3JkIDwtIHdvcmQgCiAgCiAgIyByZXRyaWV2ZSB0b3A1IHByZWNlZGluZyBjb2xsb2NhdGlvbnMKICBwcm9fcHJlIDwtIGt3aWNfcHJvICU+JQogICAgZHBseXI6OmNvdW50KHByZSkgJT4lICMgY291bnQgb2NjdXJyZW5jZXMKICAgIGFycmFuZ2UoZGVzYyhuKSkgJT4lICMgc29ydCBkZXNjZW5kaW5nCiAgICBoZWFkKG49NSkgIyBnaXZlIHRvcCA1CgogICMgcmV0cmlldmUgdG9wNSBmb2xsb3dpbmcgY29sbG9jYXRpb25zCiAgcHJvX3Bvc3QgPC0ga3dpY19wcm8gJT4lCiAgICBkcGx5cjo6Y291bnQocG9zdCkgJT4lICMgY291bnQgb2NjdXJyZW5jZXMKICAgIGFycmFuZ2UoZGVzYyhuKSkgJT4lICMgc29ydCBkZXNjZW5kaW5nCiAgICBoZWFkKG49NSkgIyBnaXZlIHRvcCA1CgogICMgbm9ybWFsaXplIGRhdGEgZnJhbWVzIHdpdGggdG9wNSBjb2xsb2NhdGlvbnMgCiAgcHJvX3ByZSRrZXl3b3JkIDwtIGtleXdvcmQgIyBhZGQga2V5d29yZCB0YWcKICBwcm9fcHJlJHRhZyA8LSAicHJlIiAjIGFkZCAicHJlIiB0YWcKICBuYW1lcyhwcm9fcHJlKVtuYW1lcyhwcm9fcHJlKSA9PSAncHJlJ10gPC0gIndvcmQiCgogIHByb19wb3N0JGtleXdvcmQgPC0ga2V5d29yZCAjIGFkZCBrZXl3b3JkIHRhZwogIHByb19wb3N0JHRhZyA8LSAicG9zdCIgIyBhZGQgInBvc3QiIHRhZwogIG5hbWVzKHByb19wb3N0KVtuYW1lcyhwcm9fcG9zdCkgPT0gJ3Bvc3QnXSA8LSAid29yZCIKICAKICAjIGNvbWJpbmUgcHJlIGFuZCBwb3N0IHRvIGZ1bGwgZGF0YSBmcmFtZSAKICBwcm9fY29sbHMxMCA8LSByYmluZChwcm9fY29sbHMxMCwgcHJvX3ByZSkKICBwcm9fY29sbHMxMCA8LSByYmluZChwcm9fY29sbHMxMCwgcHJvX3Bvc3QpfSAKYGBgCgpNb3N0IG9mIHRoZSBjb2xsb2NhdGlvbnMgb25seSBvY2N1ciBleGFjdGx5IG9uY2UuIFNpbmNlIHRoaXMgaXMgbm90IHZlcnkgaW5mb3JtYXRpdmUgZm9yIHVzLCB3ZSByZW1vdmUgYWxsIHRoZSBjb2xsb2NhdGlvbnMgd2l0aCBhIGNvdW50IG9mIGV4YWN0bHkgMS4gQWxzbywgd2Ugd2FudCB0byByZW1vdmUgbm9pc2UsIGkuZS4gZW1wdHkgc3RyaW5ncyBmcm9tIHRoZSBjb2xsb2NhdGlvbnMuIApgYGB7cn0KIyBvbmx5IGtlZXAgY29sbG9jYXRpb25zIHRoYXQgYXBwZWFyIG1vcmUgdGhhbiBvbmNlIAp0b3BfY29sbHNfcHJvPC1wcm9fY29sbHMxMFsocHJvX2NvbGxzMTAkbiA+IDEpLF0KCiMgcmVtb3ZlIGVtcHR5IHN0cmluZ3MgCnRvcF9jb2xsc19wcm88LXRvcF9jb2xsc19wcm9bKHRvcF9jb2xsc19wcm8kd29yZCA+ICIgIiksXQpgYGAKCkFuZCBzYXZlIHRoZSB0YWJsZSB0byBhIGNzdiBmaWxlLgpgYGB7cn0KI3dyaXRlLmNzdih0b3BfY29sbHNfcHJvLCAiLi4vb3V0cHV0L3RvcF9jb2xsb2NhdGlvbnNfcHJvLmNzdiIpCmBgYAoKVGhlbiwgd2UgY3JlYXRlIHRoZSBzYW1lIHRhYmxlIG9mIHRoZSB0b3AgNSBgcHJlYCBhbmQgYHBvc3RgIGNvbGxvY2F0aW9ucyBmb3IgdGhlIEMyMDIyLgpgYGB7cn0KIyBmb3IgZWFjaCBjb21wb3VuZCwgZ2V0IGxpc3Qgb2YgdG9wIDUgY29sbG9jYXRpb25zCiMgaW5pdGlhdGUgZW1wdHkgZGF0YSBmcmFtZSAKY29uX2NvbGxzMTAgPSBkYXRhLmZyYW1lKCkKCiMgZm9yIGVhY2ggY29tcG91bmQKZm9yICh3b3JkIGluIGNvbXBvdW5kcyl7CgogICMgZ2V0IGNvbGxvY2F0aW9uczogYXBwbHkga3dpYyBhbmQgdHVybiBpbnRvIHRhYmxlIAogIGt3aWNfY29uIDwtIGt3aWMoc3BfYzIwMjJfdG9rZW5zLCBwYXR0ZXJuPXdvcmQsIHdpbmRvdz0xLCB2YWx1ZXR5cGU9ImZpeGVkIikgJT4lCiAgICBhc190aWJibGUoKQoKICAjIHNldCB3b3JkIGFzIGtleXdvcmQgCiAga2V5d29yZCA8LSB3b3JkIAogIAogICMgcmV0cmlldmUgdG9wNSBwcmVjZWRpbmcgY29sbG9jYXRpb25zCiAgY29uX3ByZSA8LSBrd2ljX2NvbiAlPiUKICAgIGRwbHlyOjpjb3VudChwcmUpICU+JSAjIGNvdW50IG9jY3VycmVuY2VzCiAgICBhcnJhbmdlKGRlc2MobikpICU+JSAjIHNvcnQgZGVzY2VuZGluZwogICAgaGVhZChuPTUpICMgZ2l2ZSB0b3AgNQoKICAjIHJldHJpZXZlIHRvcDUgZm9sbG93aW5nIGNvbGxvY2F0aW9ucwogIGNvbl9wb3N0IDwtIGt3aWNfY29uICU+JQogICAgZHBseXI6OmNvdW50KHBvc3QpICU+JSAjIGNvdW50IG9jY3VycmVuY2VzCiAgICBhcnJhbmdlKGRlc2MobikpICU+JSAjIHNvcnQgZGVzY2VuZGluZwogICAgaGVhZChuPTUpICMgZ2l2ZSB0b3AgNQoKICAjIG5vcm1hbGl6ZSBkYXRhIGZyYW1lcyB3aXRoIHRvcDUgY29sbG9jYXRpb25zIAogIGNvbl9wcmUka2V5d29yZCA8LSBrZXl3b3JkICMgc2V0IGtleSB3b3JkIHRhZwogIGNvbl9wcmUkdGFnIDwtICJwcmUiICMgc2V0ICJwcmUiIHRhZwogIG5hbWVzKGNvbl9wcmUpW25hbWVzKGNvbl9wcmUpID09ICdwcmUnXSA8LSAid29yZCIKCiAgY29uX3Bvc3Qka2V5d29yZCA8LSBrZXl3b3JkICMgc2V0IGtleSB3b3JkIHRhZwogIGNvbl9wb3N0JHRhZyA8LSAicG9zdCIgIyBzZXQgInBvc3QiIHRhZwogIG5hbWVzKGNvbl9wb3N0KVtuYW1lcyhjb25fcG9zdCkgPT0gJ3Bvc3QnXSA8LSAid29yZCIKICAKICAjIGNvbWJpbmUgcHJlIGFuZCBwb3N0IHRvIGZ1bGwgZGF0YSBmcmFtZQogIGNvbl9jb2xsczEwIDwtIHJiaW5kKGNvbl9jb2xsczEwLCBjb25fcHJlKQogIGNvbl9jb2xsczEwIDwtIHJiaW5kKGNvbl9jb2xsczEwLCBjb25fcG9zdCl9CmBgYAoKQW5kLCBqdXN0IGxpa2UgYmVmb3JlLCB3ZSByZW1vdmUgdGhlIGNvbGxvY2F0aW9ucyB0aGF0IGFwcGVhcmVkIG9ubHkgb25jZSBpbiB0aGUgY29ycHVzIChhbmQgcmVtb3ZlIG5vaXNlLCBpLmUuIGVtcHR5IHN0cmluZ3MgZnJvbSB0aGUgY29sbG9jYXRpb25zKS4KYGBge3J9CiMgb25seSBrZWVwIGNvbGxvY2F0aW9ucyB0aGF0IGFwcGVhciBtb3JlIHRoYW4gb25jZSAKdG9wX2NvbGxzX2NvbjwtY29uX2NvbGxzMTBbKGNvbl9jb2xsczEwJG4gPiAxKSxdCiMgcmVtb3ZlIGVtcHR5IHN0cmluZ3MgCnRvcF9jb2xsc19jb248LXRvcF9jb2xsc19jb25bKHRvcF9jb2xsc19jb24kd29yZCA+ICIgIiksXQpgYGAKCkFuZCBzYXZlIHRoZSBmaW5hbCB0YWJsZSB0byBhIGNzdiBmaWxlLgpgYGB7cn0KI3dyaXRlLmNzdih0b3BfY29sbHNfY29uLCAiLi4vb3V0cHV0L3RvcF9jb2xsb2NhdGlvbnNfY29uLmNzdiIpCmBgYAoKIyAyLiBDb25jb3JkYW5jZXMgKEtXSUMpClRvIHJldHJpZXZlIHRoZSBjb250ZXh0IG9mIGVhY2ggY29tcG91bmQgd29yZCwgd2UgZXh0cmFjdCB0aGUgY29uY29yZGFuY2VzIG9uIGEgc2VudGVuY2UgbGV2ZWwuIFRoYXQgbWVhbnMsIHdlIGV4dHJhY3QgYSB3aW5kb3cgb2YgNSBzZW50ZW5jZXMgdG8gdGhlIGxlZnQgYW5kIHRvIHRoZSByaWdodCBvZiB0aGUga2V5d29yZCBzZW50ZW5jZS4gVG8gZG8gdGhpcywgd2UgaGF2ZSB0byB0b2tlbmlzZSBvdXIgZGF0YSBieSBzZW50ZW5jZXMsIGluc3RlYWQgb2Ygd29yZHMuCgojIyAyLjEgUHJlcHJvY2Vzc2luZyAKU2luY2Ugd2UgY2Fubm90IG5vcm1hbGlzZSB0aGUgZGF0YSB0aGUgc2FtZSB3YXkgd2hlbiB3ZSBjcmVhdGUgdG9rZW5zIG9uIGEgc2VudGVuY2UtbGV2ZWwsIHdlIGZpcnN0bHkgY3JlYXRlIHdvcmQtbGV2ZWwgdG9rZW5zIGZyb20gdGhlIGNvcnBvcmEuCmBgYHtyfQojIGNyZWF0ZSB3b3JkLWxldmVsIHRva2VucyBmb3IgUDIwMjIgYW5kIEMyMDIyCnAyMDIyX3Rva2VucyA8LSB0b2tlbnMocHJvMjAyMiwgcmVtb3ZlX3B1bmN0ID0gRkFMU0UsIHJlbW92ZV9zeW1ib2xzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnMgPSBUUlVFLCByZW1vdmVfdXJsID0gVFJVRSwgcmVtb3ZlX3NlcGFyYXRvcnMgPSBUUlVFKQoKYzIwMjJfdG9rZW5zIDwtIHRva2Vucyhjb250cmEyMDIyLCByZW1vdmVfcHVuY3QgPSBGQUxTRSwgcmVtb3ZlX3N5bWJvbHMgPSBUUlVFLAogICAgICAgICAgICAgICAgICByZW1vdmVfbnVtYmVycyA9IFRSVUUsIHJlbW92ZV91cmwgPSBUUlVFLCByZW1vdmVfc2VwYXJhdG9ycyA9IFRSVUUpCmBgYAoKVG8gdGhlc2UgdG9rZW5zLCB3ZSBhcHBseSBhIG5vcm1hbGlzYXRpb24gc3RlcCB3aGVyZSB3ZSByZW1vdmUgaHlwaGVucyB3aXRoaW4gd29yZHMsIHN1Y2ggYXMgIktsaW1hLVNrZXB0aWtlciIgdG8gY29udmVydCBpdCB0byAiS2xpbWFza2VwdGlrZXIiLiAKYGBge3J9CiMgcmVtb3ZlIGh5cGhlbnMgZnJvbSB0b2tlbnMKCiMgcmVwbGFjZSBtdWx0aS10b2tlbiBzZXF1ZW5jZXMgd2l0aCBhICJjb21wb3VuZCIgdG9rZW4gCnRva3NfY29tcF9wIDwtIHRva2Vuc19jb21wb3VuZChwMjAyMl90b2tlbnMsIHBocmFzZSgiKi0qIiksIGNvbmNhdGVuYXRvciA9IiIpCgojIGdldCB0b2tlbnMgY29udGFpbmluZyB0aGUgaHlwaGVuCnRva3NfaHlwaGVuYXRlZF9wIDwtIGdyZXAoIlxcdystXFx3KyIsIHR5cGVzKHRva3NfY29tcF9wKSwgdmFsdWUgPSBUUlVFKQoKIyByZXBsYWNlIHRoZSBoeXBoZW5hdGVkIHRva2VucyBieSB2ZXJzaW9ucyB3aXRob3V0IGh5cGhlbgpwMjAyMl90b2tzX2NsZWFuZWQgPC0gdG9rZW5zX3JlcGxhY2UodG9rc19jb21wX3AsIHRva3NfaHlwaGVuYXRlZF9wLCBnc3ViKCItIiwgIiIsIHRva3NfaHlwaGVuYXRlZF9wKSkKCiMgZG8gc2FtZSBmb3IgQzIwMjIgdG9rZW5zCnRva3NfY29tcF9jIDwtIHRva2Vuc19jb21wb3VuZChjMjAyMl90b2tlbnMsIHBocmFzZSgiKi0qIiksIGNvbmNhdGVuYXRvciA9IiIpCnRva3NfaHlwaGVuYXRlZF9jIDwtIGdyZXAoIlxcdystXFx3KyIsIHR5cGVzKHRva3NfY29tcF9jKSwgdmFsdWUgPSBUUlVFKQpjMjAyMl90b2tzX2NsZWFuZWQgPC0gdG9rZW5zX3JlcGxhY2UodG9rc19jb21wX2MsIHRva3NfaHlwaGVuYXRlZF9jLCBnc3ViKCItIiwgIiIsIHRva3NfaHlwaGVuYXRlZF9jKSkKCiMgbWVyZ2UgdG9rZW5zIGJhY2sgaW50byBjb3JwdXMgb2JqZWN0IApwMjAyMl9tZXJnZWRfdG9rcyA8LSBjb3JwdXMoc2FwcGx5KHAyMDIyX3Rva3NfY2xlYW5lZCwgcGFzdGUsIGNvbGxhcHNlID0gIiAiKSkKYzIwMjJfbWVyZ2VkX3Rva3MgPC0gY29ycHVzKHNhcHBseShjMjAyMl90b2tzX2NsZWFuZWQsIHBhc3RlLCBjb2xsYXBzZSA9ICIgIikpCmBgYAoKTm93IHdlIGNhbiBjcmVhdGUgY2xlYW5lZCBzZW50ZW5jZSB0b2tlbnMgZm9yIGJvdGggY29ycG9yYS4KYGBge3J9CiMgY3JlYXRlICJzZW50ZW5jZSIgdG9rZW5zIGZvciBQMjAyMiBhbmQgQzIwMjIgY29ycHVzCnAyMDIyX3NlbnRlbmNlcyA8LSB0b2tlbnMocDIwMjJfbWVyZ2VkX3Rva3MsIHJlbW92ZV9wdW5jdCA9IEZBTFNFLCByZW1vdmVfc3ltYm9scyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIHJlbW92ZV9udW1iZXJzID0gVFJVRSwgcmVtb3ZlX3VybCA9IFRSVUUsIHJlbW92ZV9zZXBhcmF0b3JzID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgIHdoYXQgPSAic2VudGVuY2UiKQoKYzIwMjJfc2VudGVuY2VzIDwtIHRva2VucyhjMjAyMl9tZXJnZWRfdG9rcywgcmVtb3ZlX3B1bmN0ID0gRkFMU0UsIHJlbW92ZV9zeW1ib2xzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnMgPSBUUlVFLCByZW1vdmVfdXJsID0gVFJVRSwgcmVtb3ZlX3NlcGFyYXRvcnMgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgd2hhdCA9ICJzZW50ZW5jZSIpCmBgYAoKIyMgMi4yIEtleSBXb3JkIEluIENvbnRleHQgUmV0cmlldmFsClRvIHJldHJpZXZlIHRoZSBjb25jb3JkYW5jZXMgb2YgZWFjaCBjb21wb3VuZCB3b3JkLCB3ZSBhcHBseSB0aGUgYEtleS1Xb3JkLUluLUNvbnRleHRgIGZ1bmN0aW9uIGdpdmVuIGJ5IGBxdWFudGVkYWAuCkZvciB0aGlzLCB3ZSB1c2UgdGhlIHByZXZpb3VzbHkgY3JlYXRlZCBzZW50ZW5jZSB0b2tlbnMuIApgYGB7cn0KIyBjcmVhdGUgYSBkYXRhIGZyYW1lIGZyb20gdG9rZW5zIGNvbnRhaW5pbmcgNSBzZW50ZW5jZXMgYmVmb3JlIGFuZCBhZnRlciB0aGUga2V5d29yZCAKCiMjIyBQMjAyMiAjIyMjCmt3aWNfcHJvX3NlbnQuZGYgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDcsIG5yb3cgPSAwKSkgIyBpbml0aWF0ZSBlbXB0eSBkYXRhIGZyYW1lCmt3aWNsaXN0X3NlbnRfcHJvIDwtIGxpc3QoKSAjIGluaXRpYXRlIGVtcHR5IGxpc3QKCiMgZm9yIGVhY2ggY29tcG91bmQgd29yZApmb3IgKHdvcmQgaW4gY29tcG91bmRzKQp7CiAgIyByZXRyaWV2ZSBzZW50ZW5jZXMgYmVmb3JlL2FmdGVyIGtleXdvcmQgCiAgY29udGV4dF9wcm9fc2VudCA8LSBrd2ljKHAyMDIyX3NlbnRlbmNlcywgd29yZCwgdmFsdWV0eXBlPSJyZWdleCIsIHdpbmRvdz01KQogIGt3aWNsaXN0X3NlbnRfcHJvW1t3b3JkXV0gPC0gY29udGV4dF9wcm9fc2VudCAjIHNhdmUgdG8gbGlzdCAKfQoKa3dpY19wcm9fc2VudC5kZiA9IGRvLmNhbGwocmJpbmQsIGt3aWNsaXN0X3NlbnRfcHJvKSAjIHNhdmUgdG8gZmluYWwgZGF0YSBmcmFtZSAKCiMjIyBDMjAyMiAjIyMKa3dpY19jb25fc2VudC5kZiA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sID0gNywgbnJvdyA9IDApKSAjIGluaXRpYXRlIGVtcHR5IGRhdGEgZnJhbWUKa3dpY2xpc3Rfc2VudF9jb24gPC0gbGlzdCgpICMgaW5pdGlhdGUgZW1wdHkgbGlzdAoKIyBmb3IgZWFjaCBjb21wb3VuZCB3b3JkCmZvciAod29yZCBpbiBjb21wb3VuZHMpCnsKICAjIHJldHJpZXZlIHNlbnRlbmNlcyBiZWZvcmUvYWZ0ZXIga2V5d29yZCAKICBjb250ZXh0X2Nvbl9zZW50IDwtIGt3aWMoYzIwMjJfc2VudGVuY2VzLCB3b3JkLCB2YWx1ZXR5cGU9InJlZ2V4Iiwgd2luZG93PTUpIAogIGt3aWNsaXN0X3NlbnRfY29uW1t3b3JkXV0gPC0gY29udGV4dF9jb25fc2VudCAjIHNhdmUgdG8gbGlzdAp9Cgprd2ljX2Nvbl9zZW50LmRmID0gZG8uY2FsbChyYmluZCwga3dpY2xpc3Rfc2VudF9jb24pICMgc2F2ZSB0byBmaW5hbCBkYXRhIGZyYW1lIAoKIyBnaXZlIG91dHB1dAprd2ljX3Byb19zZW50LmRmCmt3aWNfY29uX3NlbnQuZGYKYGBgCgojIDIuMiBFeHBvcnQgQ29uY29yZGFuY2VzCk5leHQsIHdlIGV4cG9ydCB0aGUgY29uY29yZGFuY2VzIHRvIHRoZSBmaWxlcyBgcHJvX2NvbnRleHRgIGFuZCBgY29uX2NvbnRleHRgLgpgYGB7cn0KIyBzYXZlIHRvIGNzdiBmaWxlIAojd3JpdGUuY3N2KGt3aWNfcHJvX3NlbnQuZGYsIi4uL291dHB1dC9wcm9fY29udGV4dC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKI3dyaXRlLmNzdihrd2ljX2Nvbl9zZW50LmRmLCIuLi9vdXRwdXQvY29uX2NvbnRleHQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKIyAzLiBUZXJtIEZyZXF1ZW5jaWVzCkFkZGl0aW9uYWxseSwgd2UgY29tcHV0ZSB0aGUgdGVybSBmcmVxdWVuY2llcyBvZiBlYWNoIGNvbXBvdW5kIHdvcmQgYW5kIHRoZSBhY2NvcmRpbmcgVEYtSURGIHNjb3JlLCBzaW5jZSBib3RoIGNvcnBvcmEgaGF2ZSBhIGRpZmZlcmVudCBzaXplIGFuZCB3ZSB3YW50IHRvIGV4cGxvcmUgdGhlIHJlbGV2YW5jZSBvZiBlYWNoIHRlcm0uIAoKQ3JlYXRlIGEgZnVuY3Rpb24gdG8gbm9ybWFsaXplIHRoZSBURi1JREYgc2NvcmVzCmBgYHtyfQojIG1pbi9tYXggbm9ybWFsaXphdGlvbiBmcm9tIC0xIHRvIDEsIHJlbGF0aXZlIHRvIGRhdGEgZnJhbWUgcmVzdWx0cwpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCwgbmEucm0gPSBUUlVFKXsKICByZXR1cm4oKHggLSBtaW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKX0KYGBgCgpXZSB1c2UgdGhlIHByZXByb2Nlc3NlZCB0b2tlbnMgZnJvbSB0aGUgYHByZXByb2Nlc3NpbmdgIG5vdGVib29rICh0aGUgdmFyaWFibGVzIGFyZSBsb2FkZWQgZnJvbSB0aGUgd29ya3NwYWNlKS4gV2UgcmUtY29tYmluZSB0aGVzZSB0b2tlbnMgdG8gZ2VuZXJhdGUgdHdvIGNvcnB1cyBvYmplY3RzLCBvbmUgZm9yIGVhY2ggc3ViZGlzY291cnNlLiBUaGVuIHdlIGNhbiBhc3NpZ24gZ3JvdXBzIHRvIHRoZSB0d28gY29ycG9yYSBhbmQgZ2VuZXJhdGUgYSBmdWxsIGNvcnB1cyB2YXJpYWJsZSBgY29tcGxldGVgIHRoYXQgY29udGFpbnMgYm90aCBjb3Jwb3JhLiBUaGlzIHN0ZXAgZW5hYmxlcyB1cyB0byBkaXJlY3RseSByZXRyaWV2ZSB0aGUgdGVybSBmcmVxdWVuY2llcyBmb3IgYm90aCBjb3Jwb3JhIGFuZCB0byBiZSBhYmxlIHRvIGdldCBhIGNvbXBhcmlzb24gdGFibGUgb2YgdGhlIGZyZXF1ZW5jaWVzLiAKYGBge3J9CiMgdXNlIGxlbW1hdGlzZWQgdG9rZW5zIHRvIHJlLWNyZWF0ZSBhIGNvcnB1cwojIHdlIG5lZWQgdGhpcyBzdGVwIGZvciB0aGUgZ3JvdXBpbmcgb2YgdGhlIGZyZXF1ZW5jaWVzIApwMjAyMl9jbGVhbmVkIDwtIGNvcnB1cyhzYXBwbHkoc3BfcDIwMjJfdG9rZW5zLCBwYXN0ZSwgY29sbGFwc2UgPSAiICIpKQpjMjAyMl9jbGVhbmVkIDwtIGNvcnB1cyhzYXBwbHkoc3BfYzIwMjJfdG9rZW5zLCBwYXN0ZSwgY29sbGFwc2UgPSAiICIpKQoKIyBjcmVhdGUgZ3JvdXBzCnAyMDIyX2NsZWFuZWQkZ3JvdXAgPC0gIlN1cHBvcnRlcnMiCmMyMDIyX2NsZWFuZWQkZ3JvdXAgPC0gIlNrZXB0aWNzIgoKIyBjcmVhdGUgYSBjb3JwdXMgY29udGFpbmluZyBib3RoIHN1YmRpc2NvdXJzZXMKY29tcGxldGUgPSBwMjAyMl9jbGVhbmVkK2MyMDIyX2NsZWFuZWQKYGBgCgpOb3cgd2UgY3JlYXRlIGEgREZNIHdpdGggdGhlIGZyZXF1ZW5jaWVzIGZvciB3aGljaCB3ZSBvbmx5IGtlZXAgdGhlIGNvbXBvdW5kIHdvcmRzLCBzaW5jZSB3ZSBhcmUgb25seSBpbnRlcmVzdGVkIGluIHRoZSB0ZXJtIGZyZXF1ZW5jaWVzIG9mIHRoZSBnbG9zc2FyeSB0ZXJtcy4gVGhpcyBERk0gaXMgdGhlbiBjb252ZXJ0ZWQgaW50byBhIGRhdGFmcmFtZSB3aGljaCBjb250YWlucyB0aGUgdGVybSBmcmVxdWVuY2llcyBmb3IgYm90aCBzdWJkaXNjb3Vyc2VzLiBUaGlzIGNhbiBub3cgYmUgc2F2ZWQgdG8gYSBmaW5hbCBmaWxlIApgYGB7cn0KIyBjcmVhdGUgZGZtIHdpdGggZnJlcXVlbmNpZXMgcGVyIGdyb3VwCmRmbV9jb21wbGV0ZV9mcmVxIDwtIGRmbShjb21wbGV0ZSkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgIGRmbV9rZWVwKHBhdHRlcm4gPSBjb21wb3VuZHMpICU+JSAjIG9ubHkga2VlcCBjb21wb3VuZCB3b3JkcwogICAgICAgICAgICAgICAgICAgICAgIGRmbV9ncm91cChncm91cHMgPSBncm91cCkgJT4lICMga2VlcCBncm91cHMgImFjdGl2aXN0cyIgYW5kICJza2VwdGljcyIKICAgICAgICAgICAgICAgICAgICAgICBkZm1fdG91cHBlcigpCgoKc2V0LnNlZWQoMykgIyBzZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnRleHRwbG90X3dvcmRjbG91ZChkZm1fY29tcGxldGVfZnJlcSwgY29tcGFyaXNvbiA9IFRSVUUsIG1heF93b3JkcyA9IDI1MCkgIyBwbG90IHdvcmRjbG91ZApgYGAKIyAzLjEgQ29tcHV0ZSBURi1JREYgU2NvcmVzCk5leHQsIHdlIGNvbXB1dGUgdGhlIFRGLUlERiBzY29yZXMgb2YgdGhlIGdsb3NzYXJ5IHRlcm1zLiBGb3IgdGhpcywgd2UgY3JlYXRlIERGTXMgZm9yIGVhY2ggc3ViZGlzY291cnNlLiBUaGlzIGlzIG5lY2Vzc2FyeSB0byBjb21wdXRlIHRoZSBURi1JREYgc2NvcmVzIGZvciBlYWNoIHRlcm0gd2l0aCByZXNwZWN0IHRvIGVhY2ggb2YgdGhlIGNvcnBvcmEuICAKYGBge3J9CiMjIyBGT1IgQzIwMjIgIyMjIAoKIyBjcmVhdGUgZGZtIGZyb20gdG9rZW5zCmRmbV9jMjAyMiA8LSBkZm0oc3BfYzIwMjJfdG9rZW5zKSAlPiUgCiAgICAgICAgICAgIGRmbV90ZmlkZigpICU+JSAjIGFwcGx5IHRmLWlkZiBjb21wdXRhdGlvbgogICAgICAgICAgICBkZm1fa2VlcChwYXR0ZXJuID0gY29tcG91bmRzKSAjIHJlbW92ZSB3b3JkcyB0aGF0IGFyZSBub3QgY29udGFpbmVkIGluIHRoZSBnbG9zc2FyeSAKCiMgYXBwbHkgbm9ybWFsaXNhdGlvbiBmdW5jdGlvbgpjMjAyMl90ZmlkZiA8LSBub3JtYWxpemUodG9wZmVhdHVyZXMoZGZtX2MyMDIyLCBuPTMwMCkpCgoKIyBjb252ZXJ0IGRmbSBpbnRvIGRhdGEgZnJhbWUgYW5kIHNvcnQgZGVzY2VuZGluZwp0b3BfYzIwMjJfbm9ybSA8LSBkYXRhLmZyYW1lKFRlcm0gPSBuYW1lcyhjMjAyMl90ZmlkZiksIEZyZXEgPSBjMjAyMl90ZmlkZiwgcm93Lm5hbWVzID0gTlVMTCkgJT4lCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhGcmVxKSkKCiMjIyBGT1IgUDIwMjIgIyMjIAoKIyBjcmVhdGUgZGZtIGZyb20gdG9rZW5zCmRmbV9wMjAyMiA8LSBkZm0oc3BfcDIwMjJfdG9rZW5zKSAlPiUgCiAgICAgICAgICAgIGRmbV90ZmlkZigpICU+JSAjIGFwcGx5IHRmLWlkZiBjb21wdXRhdGlvbgogICAgICAgICAgICBkZm1fa2VlcChwYXR0ZXJuID0gY29tcG91bmRzKSAjIHJlbW92ZSB3b3JkcyB0aGF0IGFyZSBub3QgY29udGFpbmVkIGluIHRoZSBnbG9zc2FyeSAKCiMgYXBwbHkgbm9ybWFsaXNhdGlvbiBmdW5jdGlvbgpwMjAyMl90ZmlkZiA8LSBub3JtYWxpemUodG9wZmVhdHVyZXMoZGZtX3AyMDIyLCBuPTMwMCkpCgojIGNvbnZlcnQgZGZtIGludG8gZGF0YSBmcmFtZSBhbmQgc29ydCBkZXNjZW5kaW5nCnRvcF9wMjAyMl9ub3JtIDwtIGRhdGEuZnJhbWUoVGVybSA9IG5hbWVzKHAyMDIyX3RmaWRmKSwgRnJlcSA9IHAyMDIyX3RmaWRmLCByb3cubmFtZXMgPSBOVUxMKSAlPiUKICBkcGx5cjo6YXJyYW5nZShkZXNjKEZyZXEpKQoKIyBjaGFuZ2UgY29sdW1uIG5hbWVzIHRvIGJlIGFibGUgdG8gbWVyZ2UgYm90aCBkYXRhIGZyYW1lcwpjb2xuYW1lcyh0b3BfcDIwMjJfbm9ybSlbMl0gPC0gIkZyZXFfUDIwMjIiCmNvbG5hbWVzKHRvcF9jMjAyMl9ub3JtKVsyXSA8LSAiRnJlcV9DMjAyMiIKCiMgbWVyZ2UgZGF0YSBmcmFtZXMgCmRmX21lcmdlIDwtIG1lcmdlKHRvcF9jMjAyMl9ub3JtLHRvcF9wMjAyMl9ub3JtLGJ5PSJUZXJtIiwgYWxsLnggPSBUUlVFLCBhbGwueSA9IFRSVUUpCgojIHdyaXRlIHRvIGNzdiBmaWxlCiN3cml0ZS5jc3YoZGZfbWVyZ2UsIi4uL291dHB1dC90ZmlkZl9jb21wbGV0ZS5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQpgYGAKCiMjIFBsb3QgVEYtSURGIFNjb3JlcwpUbyB2aXN1YWxpemUgYSBjb21wYXJpc29uIG9mIHRoZSBURi1JREYgc2NvcmVzIG9mIHRoZSBjb21wb3VuZCBmb3IgZWFjaCBvZiB0aGUgc3ViZGlzY291cnNlcywgd2UgY3JlYXRlIHRoZSBmb2xsb3dpbmcgcGxvdCBvZiBhIHNhbXBsZSBvZiA1MCBnbG9zc2FyeSB0ZXJtcyBhbmQgdGhlaXIgYWNjb3JkaW5nIHNjb3Jlcy4gCmBgYHtyfQojIHJldHJpZXZlIGZyZXF1ZW5jeSB0YWJsZSBvZiBkZm0KZnJlcXNfcHJvIDwtIHRleHRzdGF0X2ZyZXF1ZW5jeShkZm1fcDIwMjIsIGZvcmNlPVRSVUUpCmZyZXFzX2NvbiA8LSB0ZXh0c3RhdF9mcmVxdWVuY3koZGZtX2MyMDIyLCBmb3JjZT1UUlVFKQoKIyBjYXBpdGFsaXplIGZpcnN0IGxldHRlciBvZiBjb21wb3VuZApmcmVxc19wcm8kZmVhdHVyZSA8LSBzdHJfdG9fdGl0bGUoZnJlcXNfcHJvJGZlYXR1cmUpCmZyZXFzX2NvbiRmZWF0dXJlIDwtIHN0cl90b190aXRsZShmcmVxc19jb24kZmVhdHVyZSkKCiMgYXBwbHkgbm9ybWFsaXphdGlvbgpmcmVxc19wcm8kbm9ybWFsaXplID0gcm91bmQobm9ybWFsaXplKGZyZXFzX3BybyRmcmVxdWVuY3kpLDMpCmZyZXFzX2NvbiRub3JtYWxpemUgPSByb3VuZChub3JtYWxpemUoZnJlcXNfY29uJGZyZXF1ZW5jeSksMykKCiMgcGxvdCBjb21wYXJpc29uIG9mIGJvdGggZ3JvdXBzCmZyZXFzLmFjdCA8LSBmaWx0ZXIoZnJlcXNfcHJvKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBzZWxlY3QoZmVhdHVyZSwgbm9ybWFsaXplKQpmcmVxcy5zY2VwdCA8LSBmaWx0ZXIoZnJlcXNfY29uKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBzZWxlY3QoZmVhdHVyZSwgbm9ybWFsaXplKQpmcmVxcyA8LSBsZWZ0X2pvaW4oZnJlcXMuYWN0LCBmcmVxcy5zY2VwdCwgYnkgPSAiZmVhdHVyZSIpICU+JSBoZWFkKDMwKSAlPiUgYXJyYW5nZShub3JtYWxpemUueCkgJT4lIG11dGF0ZShmZWF0dXJlID0gZmFjdG9yKGZlYXR1cmUsIGZlYXR1cmUpKQoKIyBjcmVhdGUgcGxvdApwbG90OCA8LSBnZ3Bsb3QoZnJlcXMpICsKICAgIGdlb21fc2VnbWVudChhZXMoeD1mZWF0dXJlLCB4ZW5kPWZlYXR1cmUsIHk9bm9ybWFsaXplLngsIHllbmQ9bm9ybWFsaXplLnkpLCBjb2xvcj0iZ3JleSIpICsKICAgIGdlb21fcG9pbnQoYWVzKHg9ZmVhdHVyZSwgeT1ub3JtYWxpemUueCwgY29sb3VyPSJTdXBwb3J0ZXJzIiksIHNpemUgPSAzKSArCiAgICBnZW9tX3BvaW50KGFlcyh4PWZlYXR1cmUsIHk9bm9ybWFsaXplLnksIGNvbG91cj0iU2tlcHRpY3MiKSwgc2l6ZSA9IDMpICsKICAgIGdndGl0bGUoIkNvbXBhcmlzb24gVEYtSURGIFNjb3JlcyBwZXIgU3ViIERpc2NvdXJzZSIpICsgCiAgICB4bGFiKCIiKSArIHlsYWIoIlRGLUlERiIpICsKICAgIGNvb3JkX2ZsaXAoKQoKcGxvdDgrbGFicyhjb2xvdXI9Ikdyb3VwIikKCiMgc2F2ZSB0byBwbmcgCiNnZ3NhdmUoIi9Vc2Vycy9hbm5hL0RvY3VtZW50cy91bmkvdGhlc2lzL3Bsb3RzL2NvbXBhcmlzb25fdGZpZGYucG5nIiwgZHBpPTMwMCwgZGV2PSdwbmcnLCBoZWlnaHQ9Niwgd2lkdGg9MTEsIHVuaXRzPSJpbiIpCmBgYAojIyMgRE8gVEhJUyBBTkQgVEhFTiBTQVZFIFdPUktTUEFDRSEhISEKYGBge3J9CnNwX2MyMDIyX3Rva2VucyA8LSB0b2tlbnNfcmVwbGFjZShzcF9jMjAyMl90b2tlbnMsIHBhdHRlcm49ImtsaW1hZ2xhdWJlbnNsZWhyIiwgcmVwbGFjZW1lbnQ9ImtsaW1hZ2xhdWJlbnNsZWhyZSIsIHZhbHVldHlwZSA9ICJmaXhlZCIpCnNwX2MyMDIyX3Rva2VucyA8LSB0b2tlbnNfcmVwbGFjZShzcF9jMjAyMl90b2tlbnMsIHBhdHRlcm49ImtsaW1ha2FyYXdhbiIsIHJlcGxhY2VtZW50PSJrbGltYWthcmF3YW5lIiwgdmFsdWV0eXBlID0gImZpeGVkIikKc3BfYzIwMjJfdG9rZW5zIDwtIHRva2Vuc19yZXBsYWNlKHNwX2MyMDIyX3Rva2VucywgcGF0dGVybj0ia2xpbWF6ZXVnIiwgcmVwbGFjZW1lbnQ9ImtsaW1hemV1Z3MiLCB2YWx1ZXR5cGUgPSAiZml4ZWQiKQpzcF9jMjAyMl90b2tlbnMgPC0gdG9rZW5zX3JlcGxhY2Uoc3BfYzIwMjJfdG9rZW5zLCBwYXR0ZXJuPSJrbGltYXdlbmRlaGFsIiwgcmVwbGFjZW1lbnQ9ImtsaW1hd2VuZGVoYWxzIiwgdmFsdWV0eXBlID0gImZpeGVkIikKc3BfcDIwMjJfdG9rZW5zIDwtIHRva2Vuc19yZXBsYWNlKHNwX3AyMDIyX3Rva2VucywgcGF0dGVybj0ia2xpbWFnbGF1YmVuc2xlaHIiLCByZXBsYWNlbWVudD0ia2xpbWFnbGF1YmVuc2xlaHJlIiwgdmFsdWV0eXBlID0gImZpeGVkIikKc3BfcDIwMjJfdG9rZW5zIDwtIHRva2Vuc19yZXBsYWNlKHNwX3AyMDIyX3Rva2VucywgcGF0dGVybj0ia2xpbWFrYXJhd2FuIiwgcmVwbGFjZW1lbnQ9ImtsaW1ha2FyYXdhbmUiLCB2YWx1ZXR5cGUgPSAiZml4ZWQiKQpzcF9wMjAyMl90b2tlbnMgPC0gdG9rZW5zX3JlcGxhY2Uoc3BfcDIwMjJfdG9rZW5zLCBwYXR0ZXJuPSJrbGltYXpldWciLCByZXBsYWNlbWVudD0ia2xpbWF6ZXVncyIsIHZhbHVldHlwZSA9ICJmaXhlZCIpCnNwX3AyMDIyX3Rva2VucyA8LSB0b2tlbnNfcmVwbGFjZShzcF9wMjAyMl90b2tlbnMsIHBhdHRlcm49ImtsaW1hd2VuZGVoYWwiLCByZXBsYWNlbWVudD0ia2xpbWF3ZW5kZWhhbHMiLCB2YWx1ZXR5cGUgPSAiZml4ZWQiKQpgYGAKCgoK